#include "intersection.h"
#include "transform.h"
#include "shape.h"

void Intersection::ComputePartials(const RayDifferential &ray) const{
	float A[2][2], Bx[2], By[2], x[2];
	int axes[2];

	/* Compute the texture coordinates partials wrt. 
	   changes in the screen-space position. Based on PBRT */
	if (hasUVPartials)
		return;
	hasUVPartials = true;

	if (!ray.hasDifferentials || (dpdu.IsZero() && dpdv.IsZero())) {
		dudx = dvdx = dudy = dvdy = 0.0f;
		return;
	}

	/* Offset of the plane passing through the surface */
	const float d = -Dot(geometryFrame.n, Vector3(p));

	const float txRecip = Dot(geometryFrame.n, ray.rxDirection),
				tyRecip = Dot(geometryFrame.n, ray.ryDirection);

	if (isnan(txRecip)|| isnan(tyRecip)) {
		dudx = dvdx = dudy = dvdy = 0.0f;
		
		return;
	}

	/* Ray distances traveled */
	const float tx = -(Dot(geometryFrame.n, Vector3(ray.rxOrigin)) + d) / 
		txRecip;
	const float ty = -(Dot(geometryFrame.n, Vector3(ray.ryOrigin)) + d) / 
		tyRecip;

	/* Calculate the U and V partials by solving two out
	   of a set of 3 equations in an overconstrained system */
	float absX = std::abs(geometryFrame.n.x),
		  absY = std::abs(geometryFrame.n.y),
		  absZ = std::abs(geometryFrame.n.z);

	if (absX > absY && absX > absZ) {
		axes[0] = 1; axes[1] = 2;
	} else if (absY > absZ) {
		axes[0] = 0; axes[1] = 2;
	} else {
		axes[0] = 0; axes[1] = 1;
	}

	A[0][0] = dpdu[axes[0]];
	A[0][1] = dpdv[axes[0]];
	A[1][0] = dpdu[axes[1]];
	A[1][1] = dpdv[axes[1]];

	/* Auxilary intersection point of the adjacent rays */
	Point3 px = ray.rxOrigin + ray.rxDirection * (tx), py = ray.ryOrigin + ray.ryDirection * (ty);
	Bx[0] = px[axes[0]] - p[axes[0]];
	Bx[1] = px[axes[1]] - p[axes[1]];
	By[0] = py[axes[0]] - p[axes[0]];
	By[1] = py[axes[1]] - p[axes[1]];
	
	if (!(SolveLinearSystem2x2(A, Bx, &x[0], &x[1]))) {
		dudx = x[0]; dvdx = x[1];
	} else {
		dudx = 1; dvdx = 0;
	}

	if (!(SolveLinearSystem2x2(A, By, &x[0], &x[1]))) {
		dudy = x[0]; dvdy = x[1];
	} else {
		dudy = 0; dudy = 1;
	}
}

BSDF *Intersection::GetBSDF(const RayDifferential &ray)  {
	ComputePartials(ray);
	return shape->GetBSDF(*this);
}

std::string Intersection::ToString() const {
	if (!IsValid())
		return "Intersection[invalid]";
	std::ostringstream oss;
	oss << "Intersection[" << std::endl
		<< "  p = " << p.ToString() << "," << std::endl
		<< "  t = " << t << "," << std::endl
		<< "  geoFrame = " << geometryFrame.ToString() << "," << std::endl
		<< "  shFrame = " << shadingFrame.ToString() << "," << std::endl
		<< "  uv = " << uv.ToString() << "," << std::endl
		<< "  hasUVPartials = " << hasUVPartials << "," << std::endl
		<< "  dpdu = " << dpdu.ToString() << "," << std::endl
		<< "  dpdv = " << dpdv.ToString() << "," << std::endl
		<< "  time = " << time << "," << std::endl
		//Note: not implemented yet!!
		<< "  shape = " << "To be implemented" << std::endl
		<< "]";
	return oss.str();
}